﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using HDK.UI.Controls.Primitives;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;

namespace HDK.UI.Controls
{
    public class ParallaxPanel : AnimatingPanel
    {
        public ParallaxPanel()
        {
            // set default values (TODO: move to metadata)
            this.FocalLength = 100;
            this.CameraX = 0;
            this.CameraY = 0;
            this.CameraZ = 0;

            // inintialize our default locations collection (so we can set it in markup)
            this.DefaultLocations = new List<Point3D>();
        }

        #region AdjustOpacity (DependencyProperty)

        public static readonly DependencyProperty AdjustOpacityProperty =
            DependencyProperty.RegisterAttached("AdjustOpacity", typeof(bool), typeof(ParallaxPanel), new PropertyMetadata(new PropertyChangedCallback(OnAdjustOpacityChanged)));

        public static void SetAdjustOpacity(DependencyObject o, bool value)
        {
            o.SetValue(AdjustOpacityProperty, value);
        }

        public static bool GetAdjustOpacity(DependencyObject o)
        {
            return (bool)o.GetValue(AdjustOpacityProperty);
        }

        private static void OnAdjustOpacityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {

        }

        #endregion

        #region CameraX (DependencyProperty)

        public double CameraX
        {
            get { return (double)GetValue(CameraXProperty); }
            set { SetValue(CameraXProperty, value); }
        }
        public static readonly DependencyProperty CameraXProperty =
            DependencyProperty.Register("CameraX", typeof(double), typeof(ParallaxPanel), new PropertyMetadata(0, new PropertyChangedCallback(OnCameraXChanged)));

        private static void OnCameraXChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((ParallaxPanel)d).OnCameraXChanged(e);
        }

        protected virtual void OnCameraXChanged(DependencyPropertyChangedEventArgs e)
        {
            InvalidateArrange();
        }

        #endregion

        #region CameraY (DependencyProperty)

        public double CameraY
        {
            get { return (double)GetValue(CameraYProperty); }
            set { SetValue(CameraYProperty, value); }
        }
        public static readonly DependencyProperty CameraYProperty =
            DependencyProperty.Register("CameraY", typeof(double), typeof(ParallaxPanel), new PropertyMetadata(0, new PropertyChangedCallback(OnCameraYChanged)));

        private static void OnCameraYChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((ParallaxPanel)d).OnCameraYChanged(e);
        }

        protected virtual void OnCameraYChanged(DependencyPropertyChangedEventArgs e)
        {
            InvalidateArrange();
        }

        #endregion

        #region CameraZ (DependencyProperty)

        public double CameraZ
        {
            get { return (double)GetValue(CameraZProperty); }
            set { SetValue(CameraZProperty, value); }
        }
        public static readonly DependencyProperty CameraZProperty =
            DependencyProperty.Register("CameraZ", typeof(double), typeof(ParallaxPanel), new PropertyMetadata(0, new PropertyChangedCallback(OnCameraZChanged)));

        private static void OnCameraZChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((ParallaxPanel)d).OnCameraZChanged(e);
        }

        protected virtual void OnCameraZChanged(DependencyPropertyChangedEventArgs e)
        {
            InvalidateArrange();
        }

        #endregion

        #region FocalLength (DependencyProperty)

        public double FocalLength
        {
            get { return (double)GetValue(FocalLengthProperty); }
            set { SetValue(FocalLengthProperty, value); }
        }
        public static readonly DependencyProperty FocalLengthProperty =
            DependencyProperty.Register("FocalLength", typeof(double), typeof(ParallaxPanel), new PropertyMetadata(new PropertyChangedCallback(OnFocalLengthChanged)));

        private static void OnFocalLengthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((ParallaxPanel)d).OnFocalLengthChanged(e);
        }

        protected virtual void OnFocalLengthChanged(DependencyPropertyChangedEventArgs e)
        {
        }

        #endregion

        #region X (DependencyProperty)

        public static readonly DependencyProperty XProperty =
            DependencyProperty.RegisterAttached("X", typeof(double), typeof(ParallaxPanel), new PropertyMetadata(0.0, new PropertyChangedCallback(OnXChanged)));

        public static void SetX(DependencyObject o, double value)
        {
            o.SetValue(XProperty, value);
        }

        public static double GetX(DependencyObject o)
        {
            return (double)o.GetValue(XProperty);
        }

        private static void OnXChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ParallaxPanel parent = VisualTreeHelper.GetParent(d) as ParallaxPanel;
            if (parent != null)
            {
                parent.InvalidateMeasure();
            }
        }

        #endregion

        #region Y (DependencyProperty)

        public static readonly DependencyProperty YProperty =
            DependencyProperty.RegisterAttached("Y", typeof(double), typeof(ParallaxPanel), new PropertyMetadata(0.0, new PropertyChangedCallback(OnYChanged)));

        public static void SetY(DependencyObject o, double value)
        {
            o.SetValue(YProperty, value);
        }

        public static double GetY(DependencyObject o)
        {
            return (double)o.GetValue(YProperty);
        }

        private static void OnYChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ParallaxPanel parent = VisualTreeHelper.GetParent(d) as ParallaxPanel;
            if (parent != null)
            {
                parent.InvalidateMeasure();
            }
        }

        #endregion

        #region Z (DependencyProperty)

        public static readonly DependencyProperty ZProperty =
            DependencyProperty.RegisterAttached("Z", typeof(double), typeof(ParallaxPanel), new PropertyMetadata(0.0, new PropertyChangedCallback(OnZChanged)));

        public static void SetZ(DependencyObject o, double value)
        {
            o.SetValue(ZProperty, value);
        }

        public static double GetZ(DependencyObject o)
        {
            return (double)o.GetValue(ZProperty);
        }

        private static void OnZChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ParallaxPanel parent = VisualTreeHelper.GetParent(d) as ParallaxPanel;
            if (parent != null)
            {
                parent.InvalidateMeasure();
            }
        }

        #endregion


        #region ApplyScaleTransform (Attached DependencyProperty)

        public static readonly DependencyProperty ApplyScaleTransformProperty =
            DependencyProperty.RegisterAttached("ApplyScaleTransform", typeof(bool), typeof(ParallaxPanel), new PropertyMetadata(true, new PropertyChangedCallback(OnApplyScaleTransformChanged)));

        public static void SetApplyScaleTransform(DependencyObject o, bool value)
        {
            o.SetValue(ApplyScaleTransformProperty, value);
        }

        public static bool GetApplyScaleTransform(DependencyObject o)
        {
            return (bool)o.GetValue(ApplyScaleTransformProperty);
        }

        private static void OnApplyScaleTransformChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {

        }

        #endregion


        #region DefaultLocations (DependencyProperty)

        public List<Point3D> DefaultLocations
        {
            get { return (List<Point3D>)GetValue(DefaultLocationsProperty); }
            set { SetValue(DefaultLocationsProperty, value); }
        }
        public static readonly DependencyProperty DefaultLocationsProperty =
            DependencyProperty.Register("DefaultLocations", typeof(List<Point3D>), typeof(ParallaxPanel),
            new PropertyMetadata(null, new PropertyChangedCallback(OnDefaultLocationsChanged)));

        private static void OnDefaultLocationsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((ParallaxPanel)d).OnDefaultLocationsChanged(e);
        }

        protected virtual void OnDefaultLocationsChanged(DependencyPropertyChangedEventArgs e)
        {
        }

        #endregion



        // The MeasureOverride method is your chance to ask
        // all of your children how big they want to be

        protected override Size MeasureOverride(Size availableSize)
        {
            Size YouCanBeAsBigAsYouWant = new Size(double.PositiveInfinity, double.PositiveInfinity);

            foreach (UIElement element in this.Children)
            {
                element.Measure(YouCanBeAsBigAsYouWant);
            }

            return new Size();
        }

        // The ArrangeOverride method is your chance to place
        // and size all of your children


        protected override Size ArrangeOverride(Size finalSize)
        {
            int defaultLocationIndex = 0;

            foreach (UIElement element in this.Children)
            {
                double x = (double)element.GetValue(XProperty);
                double y = (double)element.GetValue(YProperty);
                double z = (double)element.GetValue(ZProperty);

                // if the value is NAN then we set it the next value from our default locations.
                // we update the value directly on the object so that we don't hit this loop on 
                if (double.IsNaN(x) || double.IsNaN(y) || double.IsNaN(z))
                {
                    if (this.DefaultLocations != null && this.DefaultLocations.Count > 0)
                    {
                        if (defaultLocationIndex < 0) defaultLocationIndex = 0;
                        if (defaultLocationIndex > this.DefaultLocations.Count - 1) defaultLocationIndex = 0;

                        Point3D p = DefaultLocations[defaultLocationIndex];
                        defaultLocationIndex++;

                        //element.SetValue(XProperty, (double)p.X);
                        //element.SetValue(YProperty, (double)p.Y);
                        //element.SetValue(ZProperty, (double)p.Z);

                        x = p.X;
                        y = p.Y;
                        z = p.Z;
                    }
                    else
                    {
                        base.SetElementLocation(element, new Rect(0, 0, 0, 0), false);
                    }
                }

                double transx = x - this.CameraX;
                double transy = y - this.CameraY;

                double zscaler = Math.Max(FocalLength + z - CameraZ, 0.000000001);
                double scaleRatio = FocalLength / zscaler;

                // first, position things in the canvas with X and Y


                double left = Math.Round(x + (finalSize.Width / 2) + (transx * scaleRatio));
                double top = Math.Round(y + (finalSize.Height / 2) + (transy * scaleRatio));

                base.SetElementLocation(element, new Rect(left, top, element.DesiredSize.Width, element.DesiredSize.Height));

                bool applyScale = (bool)element.GetValue(ApplyScaleTransformProperty);

                if (applyScale)
                {
                    element.SetValue(Canvas.ZIndexProperty, (int)(z * -1));

                    ScaleTransform scale = (element.RenderTransform as ScaleTransform);

                    if ((scale != null && (!IsClose(scale.ScaleX, scaleRatio) || !IsClose(scale.ScaleY, scaleRatio))) || scale == null)
                    {
                        if (scale == null)
                        {
                            scale = new ScaleTransform();
                            element.RenderTransform = scale;
                            element.RenderTransformOrigin = new Point(0.5, 0.5);

                        }

                        scale.ScaleX = scaleRatio;
                        scale.ScaleY = scaleRatio;
                    }
                }
                //bool adjustOpacity = (bool) element.GetValue(AdjustOpacityProperty);
                //if (adjustOpacity) element.Opacity = scaleRatio - 0.4;
            }

            return finalSize;
        }


        bool IsClose(double a, double b)
        {
            double v = Math.Abs(a - b);
            if (v < 0.0001) return true;
            return false;
        }
    }

    public class Point3D
    {
        public double X { get; set; }
        public double Y { get; set; }
        public int Z { get; set; }
    }
}